'''
Created on 20/07/2009

@author: LGustavo
'''
from pydssim.network.protocol.dht.kademlia.i_encoding import IEncoding 

class Bencode(IEncoding):
    """ Implementation of a Bencode-based algorithm (Bencode is the encoding
    algorithm used by Bittorrent).
    
    @note: This algorithm differs from the "official" Bencode algorithm in
           that it can encode/decode floating point values in addition to
           integers.
    """
    
    def encode(self, data):
        """ Encoder implementation of the Bencode algorithm
        
        @param data: The data to encode
        @type data: int, long, tuple, list, dict or str
        
        @return: The encoded data
        @rtype: str
        """
       
        if type(data) in (int, long):
            return 'i%de' % data
        elif type(data) == str:
            return '%d:%s' % (len(data), data)
        elif type(data) in (list, tuple):
            encodedListItems = ''
            for item in data:
                encodedListItems += self.encode(item)
            return 'l%se' % encodedListItems
        elif type(data) == dict:
            encodedDictItems = ''
            keys = data.keys()
            keys.sort()
            for key in keys:
                encodedDictItems += self.encode(key)
                encodedDictItems += self.encode(data[key])
            return 'd%se' % encodedDictItems
        elif type(data) == float:
            # This (float data type) is a non-standard extension to the original Bencode algorithm 
            return 'f%fe' % data
        elif data == None:
            # This (None/NULL data type) is a non-standard extension to the original Bencode algorithm 
            return 'n'
        else:
            raise TypeError, "Cannot bencode '%s' object" % type(data)
    
    def decode(self, data):
        """ Decoder implementation of the Bencode algorithm 
        
        @param data: The encoded data
        @type data: str
        
        @note: This is a convenience wrapper for the recursive decoding
               algorithm, C{_decodeRecursive}
       
        @return: The decoded data, as a native Python type
        @rtype:  int, list, dict or str
        """
        if len(data) == 0:
            raise DecodeError, 'Cannot decode empty string'
        return self._decodeRecursive(data)[0]
    
    @staticmethod
    def _decodeRecursive(data, startIndex=0):
        """ Actual implementation of the recursive Bencode algorithm
        
        Do not call this; use C{decode()} instead
        """
        if data[startIndex] == 'i':
            endPos = data[startIndex:].find('e')+startIndex
            return (int(data[startIndex+1:endPos]), endPos+1)
        elif data[startIndex] == 'l':
            startIndex += 1
            decodedList = []
            while data[startIndex] != 'e':
                listData, startIndex = Bencode._decodeRecursive(data, startIndex)
                decodedList.append(listData)
            return (decodedList, startIndex+1)
        elif data[startIndex] == 'd':
            startIndex += 1
            decodedDict = {}
            while data[startIndex] != 'e':
                key, startIndex = Bencode._decodeRecursive(data, startIndex)
                value, startIndex = Bencode._decodeRecursive(data, startIndex)
                decodedDict[key] = value
            return (decodedDict, startIndex)
        elif data[startIndex] == 'f':
            # This (float data type) is a non-standard extension to the original Bencode algorithm
            endPos = data[startIndex:].find('e')+startIndex
            return (float(data[startIndex+1:endPos]), endPos+1)
        elif data[startIndex] == 'n':
            # This (None/NULL data type) is a non-standard extension to the original Bencode algorithm 
            return (None, startIndex+1)
        else:
            splitPos = data[startIndex:].find(':')+startIndex
            try:
                length = int(data[startIndex:splitPos])
            except ValueError, e:
                raise DecodeError, e
            startIndex = splitPos+1
            endPos = startIndex+length
            bytes = data[startIndex:endPos]
            return (bytes, endPos)

